/*
 * @(#)SelectorModule.java
 *
 * Copyright 2003-2004 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistribution of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 * 
 *   2. Redistribution in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed or intended for use in
 * the design, construction, operation or maintenance of any nuclear facility.
 */

package com.sun.xacml.finder.impl;

import com.sun.org.apache.xpath.internal.functions.FuncCount;
import com.sun.xacml.AbstractPolicy;
import com.sun.xacml.EvaluationCtx;
import com.sun.xacml.ParsingException;
import com.sun.xacml.UnknownIdentifierException;
import main.Variables;

import com.sun.xacml.attr.AttributeFactory;
import com.sun.xacml.attr.AttributeValue;
import com.sun.xacml.attr.BagAttribute;
import com.sun.xacml.attr.BoxAttribute;
import com.sun.xacml.attr.GeometryAttribute;
import com.sun.xacml.attr.PointAttribute;

import com.sun.xacml.cond.EvaluationResult;

import com.sun.xacml.ctx.Status;

import com.sun.xacml.finder.AttributeFinderModule;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringBufferInputStream;
import java.net.URI;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*;

import org.apache.xalan.extensions.XPathFunctionImpl;
import org.apache.xpath.XPathAPI;
import org.apache.xpath.axes.NodeSequence;
import org.apache.xpath.functions.Function;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.xpath.XPathExpression;
import org.xml.sax.SAXException;

import sun.security.pkcs11.wrapper.Functions;

/**
 * This module implements the basic behavior of the AttributeSelectorType,
 * looking for attribute values in the physical request document using the given
 * XPath expression. This is implemented as a separate module (instead of being
 * implemented directly in <code>AttributeSelector</code> so that programmers
 * can remove this functionality if they want (it's optional in the spec), so
 * they can replace this code with more efficient, specific code as needed, and
 * so they can easily swap in different XPath libraries.
 * <p>
 * Note that if no matches are found, this module will return an empty bag
 * (unless some error occurred). The <code>AttributeSelector</code> is still
 * deciding what to return to the policy based on the MustBePresent attribute.
 * <p>
 * This module uses the Xalan XPath implementation, and supports only version
 * 1.0 of XPath. It is a fully functional, correct implementation of XACML's
 * AttributeSelector functionality, but is not designed for environments that
 * make significant use of XPath queries. Developers for any such environment
 * should consider implementing their own module.
 * 
 * @since 1.0
 * @author Seth Proctor
 */
public class SpatialSelectorModule extends AttributeFinderModule {

	/**
	 * Returns true since this module supports retrieving attributes based on
	 * the data provided in an AttributeSelectorType.
	 * 
	 * @return true
	 */
	public boolean isSelectorSupported() {
		return true;
	}

	/**
	 * Private helper to create a new processing error status result
	 */
	private EvaluationResult createProcessingError(String msg) {
		ArrayList code = new ArrayList();
		code.add(Status.STATUS_PROCESSING_ERROR);
		return new EvaluationResult(new Status(code, msg));
	}

	/**
	 * Tries to find attribute values based on the given selector data. The
	 * result, if successful, always contains a <code>BagAttribute</code>, even
	 * if only one value was found. If no values were found, but no other error
	 * occurred, an empty bag is returned.
	 * 
	 * @param path
	 *            the XPath expression to search against
	 * @param namespaceNode
	 *            the DOM node defining namespace mappings to use, or null if
	 *            mappings come from the context root
	 * @param type
	 *            the datatype of the attributes to find
	 * @param context
	 *            the representation of the request data
	 * @param xpathVersion
	 *            the XPath version to use
	 * 
	 * @return the result of attribute retrieval, which will be a bag of
	 *         attributes or an error
	 * @throws JDOMException
	 * @throws IOException
	 */
	public EvaluationResult findAttribute(String path, Node namespaceNode,
			URI type, EvaluationCtx context, String xpathVersion)
			throws JDOMException, IOException {
		// we only support 1.0
		if (!xpathVersion.equals(AbstractPolicy.XPATH_1_0_VERSION))
			return new EvaluationResult(BagAttribute.createEmptyBag(type));

		// get the root of the resource content document
		SAXBuilder builder = new SAXBuilder();
		
		// get request file name from Variables
		// create document from request file
		String requestFile = Variables.requestFileName;
		Document doc = builder.build(new FileInputStream(requestFile));
		
		//Xpath cho resource content
		XPath xPath = XPath
				.newInstance("//xacml-context:Request/xacml-context:Resource/xacml-context:ResourceContent/*");
		Element resourceContentRoot = (Element) xPath.selectSingleNode(doc);
		Node root = context.getRequestRoot();
		
		//Xpath cho subject location
		XPath xPathSubjectLoc = XPath.newInstance("//xacml-context:Request/xacml-context:Subject/xacml-context:SubjectLocation/*");
		Element subjectLocationRoot = (Element) xPathSubjectLoc.selectSingleNode(doc);

		// if we were provided with a non-null namespace node, then use it
		// to resolve namespaces, otherwise use the context root node
		Node nsNode = (namespaceNode != null) ? namespaceNode : root;

		// setup the root path (pre-pended to the context path), which...
		String rootPath = "";

		// ...only has content if the context path is relative
		if (path.charAt(0) != '/') {
			String rootName = root.getLocalName();

			// see if the request root is in a namespace
			String namespace = root.getNamespaceURI();

			if (namespace == null) {
				// no namespacing, so we're done
				rootPath = "/" + rootName + "/";
			} else {
				// namespaces are used, so we need to lookup the correct
				// prefix to use in the search string
				NamedNodeMap nmap = namespaceNode.getAttributes();
				rootPath = null;

				for (int i = 0; i < nmap.getLength(); i++) {
					Node n = nmap.item(i);
					if (n.getNodeValue().equals(namespace)) {
						// we found the matching namespace, so get the prefix
						// and then break out
						String name = n.getNodeName();
						int pos = name.indexOf(':');

						if (pos == -1) {
							// the namespace was the default namespace
							rootPath = "/";
						} else {
							// we found a prefixed namespace
							rootPath = "/" + name.substring(pos + 1);
						}

						// finish off the string
						rootPath += ":" + rootName + "/";

						break;
					}
				}

				// if the rootPath is still null, then we don't have any
				// definitions for the namespace
				if (rootPath == null)
					return createProcessingError("Failed to map a namespace"
							+ " in an XPath expression");
			}
		}
		
		
		AttributeFactory attrFactory = AttributeFactory.getInstance();
		ArrayList list = new ArrayList();
		
		//Rut trich attibute tu Resource Content
		if(GeometryAttribute.identifier.equals(type.toString())){
			// now do the query, pre-pending the root path to the context path
			List matches = null;
			NodeList matches1 = null;
			try {
				XPath xPath2 = XPath.newInstance(path);
				// kiem tra xpath dan den mot resource content hay subject location
				matches = xPath2.selectNodes(resourceContentRoot);
				if(matches == null)
				{
					matches = xPath2.selectNodes(subjectLocationRoot);
				}
				// get node list by xpath
				matches1 = XPathAPI.selectNodeList(root, rootPath + path, nsNode);
				
			} catch (Exception e) {
				// in the case of any exception, we need to return an error
				return createProcessingError("error in XPath: " + e.getMessage());
			}

			if (matches.size() == 0) {
				// we didn't find anything, so we return an empty bag
				return new EvaluationResult(BagAttribute.createEmptyBag(type));
			}			
			
			for (int i = 0; i < matches.size(); i++) {
				if (Element.class == matches.get(i).getClass()) {
					 Element node = (Element) matches.get(i);
					
					System.out.println("matches " + matches.get(i));
					for (int ix = 0; ix < resourceContentRoot.getAdditionalNamespaces().size(); ix++)
						node.addNamespaceDeclaration((Namespace) resourceContentRoot.getAdditionalNamespaces().get(ix));				
					// handle spatial attribute types
					if (GeometryAttribute.identifier.equals(type.toString())) {
						List<Element> childList = node.getChildren();
						ArrayList<Element> elementList = new ArrayList();
						Iterator iterator = childList.iterator();
						while (iterator.hasNext())
							elementList.add((Element) iterator.next());

						// Check geometry type of node
						Element firstElement = elementList.get(0);
						if (firstElement.getName() == "outerBoundaryIs") {
							node.setName("Polygon");
						} else if (firstElement.getName() == "coordinates") {
							// cs: dau phan cach cac point trong chuoi point
							// decimal: dau phan cach phan nguyen va phan thap phan
							String cs = ","; // Mac dinh cs = ","
							String decimal = "."; // Mac dinh ts = "."

							// Kiem tra cs co duoc qui dinh lai trong coordinates
							// neu co, gan gia tri duoc qui dinh lai cho bien cs
							cs = firstElement.getAttributeValue("cs") != null ? firstElement
									.getAttributeValue("cs")
									: ",";

							// Kiem tra xem trong chuoi gia tri cua coordinates co
							// chua cs hay khong
							if (firstElement.getValue().contains(cs)) {
								// Trong truong hop chuoi gia tri cua coordinates
								// khong chua cs
								// Kiem tra decimal co duoc qui dinh lai hay khong
								// neu co, gan gia tri duoc qui dinh lai cho bien
								// decimal
								decimal = firstElement.getAttributeValue("decimal") != null ? firstElement
										.getAttributeValue("decial")
										: ".";
								// Kiem tra chuoi gia tri cua cooedinates co chua
								// decimal qui dinh lai ko
								// Truong hop nay xay ra khi decimal duoc qui dinh
								// lai la ","
								if (firstElement.getAttributeValue("decimal") == decimal)
									node.setName("Point");
								else
									node.setName("LineString");
							}
							// Neu chuoi gia tri cua coordinates co chua cs
							else
								node.setName("Point");
						} else if (firstElement.getName() == "coord") {
							// Neu toa do chi chua mot phan tu coord
							if (elementList.size() == 1)
								node.setName("Point");
							else
								node.setName("LineString");
						}
						list.add(new GeometryAttribute(node.getParentElement()));
					}		
				}
			}			
		}
		else {
			QName qName; 
			
			//Tra ve kieu QName tuong ung voi type tra ve
			if(type.toString().equals("http://www.w3.org/2001/XMLSchema#string"))
				qName = XPathConstants.STRING;
			else if((type.toString().equals("http://www.w3.org/2001/XMLSchema#double"))||
					(type.toString().equals("http://www.w3.org/2001/XMLSchema#integer")))
				qName = XPathConstants.NUMBER;
			else if(type.toString().equals("http://www.w3.org/2001/XMLSchema#boolean"))
				qName = XPathConstants.BOOLEAN;
			else qName = XPathConstants.NODESET;			
			
			DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
		    domFactory.setNamespaceAware(true); // never forget this!
			javax.xml.xpath.XPath xpath1 = XPathFactory.newInstance().newXPath();
			NodeList nodes = null;
			try {
				//Su dung XPathExpression xu ly XPathContextPath trong truong hop type tra ve 
				DocumentBuilder builder1 = domFactory.newDocumentBuilder();
				xpath1.setNamespaceContext(new PersonalNamespaceContext());
				javax.xml.xpath.XPathExpression expr = xpath1.compile(path);
				org.w3c.dom.Document doc1 = builder1.parse(requestFile);
				Object result = expr.evaluate(doc1, qName);
				System.out.println("result: " + result.toString());
				
				if(qName == XPathConstants.NODESET)
				{
					nodes = (NodeList) result;
					//System.out.println("NodeSet: " + nodes.item(0));
					try {

						for (int i = 0; i < nodes.getLength(); i++) {
							String text = null;
							Node node = nodes.item(i);
							short nodeType = node.getNodeType();

							// see if this is straight text, or a node with data under
							// it and then get the values accordingly
							if ((nodeType == Node.CDATA_SECTION_NODE)
									|| (nodeType == Node.COMMENT_NODE)
									|| (nodeType == Node.TEXT_NODE)
									|| (nodeType == Node.ATTRIBUTE_NODE)) {
								// there is no child to this node
								text = node.getNodeValue();
								
							} else {
								// the data is in a child node
								text = node.getFirstChild().getNodeValue();
							}
							if (text != null)
								list.add(attrFactory.createValue(type, text));
						}

					} catch (ParsingException pe) {
						return createProcessingError(pe.getMessage());
					} catch (UnknownIdentifierException uie) {
						return createProcessingError("unknown attribute type: " + type);
					}
					
				}
				else if(qName == XPathConstants.NUMBER){
					double doubleResult = Double.parseDouble(result.toString());
					
					int intResult = (int) doubleResult;
					System.out.println("int " + intResult);
					list.add(attrFactory.createValue(type, String.valueOf(intResult)));
				}
				else
					list.add(attrFactory.createValue(type, result.toString()));
				System.out.println("xpath function " + result);  
			} catch (XPathExpressionException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (SAXException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ParserConfigurationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (UnknownIdentifierException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ParsingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (!GeometryAttribute.identifier.equals(type.toString()))
				return new EvaluationResult(new BagAttribute(type, list));
		}
		
		return new EvaluationResult(new BagAttribute(type, list));		

	}

}
